package com.jxmcloud.business.cut.aspect;

import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import com.jxmcloud.business.cut.props.CutProperties;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@RequiredArgsConstructor
@Configuration
public class LogInterceptor implements MethodInterceptor {

	private final CutProperties properties;

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		if (!properties.getEnableLog()) {
			return invocation.proceed();
		}

		Object invokeResult = null;
		TimeInterval timer = new TimeInterval();
		try {
			invokeResult = invocation.proceed();
		} catch (Exception exception) {
			invokeResult = exception;
			throw exception;
		} finally {
			// 确保无论是否有异常都会记录日志
			if (properties.getEnableLog()) {
				long timeConsuming = timer.intervalMs();
				LogContext context = buildLogContext(invocation, timeConsuming, invokeResult);
				ThreadUtil.execute(() -> printFullLog(context));
			}
		}

		return invokeResult;
	}

	private LogContext buildLogContext(MethodInvocation invocation, long timeConsuming, Object result) {
		Method method = invocation.getMethod();
		Object[] filteredArgs = filterArguments(invocation.getArguments());
		LogContext logContext = new LogContext();
		logContext.setMethod(method);
		logContext.setArgs(filteredArgs);
		logContext.setResult(result);
		logContext.setTimeConsuming(timeConsuming);
		logContext.setRequestInfo(buildRequestInfo());
		return logContext;
	}

	private RequestInfo buildRequestInfo() {
		RequestInfo requestInfo = new RequestInfo();
		requestInfo.setThreadName(Thread.currentThread().getName());

		ServletRequestAttributes attributes =
				(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		if (attributes == null) {
			return requestInfo;
		}

		HttpServletRequest request = attributes.getRequest();
		requestInfo.setMethod(request.getMethod());
		requestInfo.setUrl(request.getRequestURL().toString());
		return requestInfo;
	}

	private Object[] filterArguments(Object[] arguments) {
		if (ArrayUtil.isEmpty(arguments)) {
			return new Object[0];
		}

		return Arrays.stream(arguments)
				.filter(arg -> !(arg instanceof ServletRequest || arg instanceof ServletResponse))
				.toArray();
	}

	private void printFullLog(LogContext context) {
		String logContent = buildLogContent(context);
		log.info(logContent);
	}

	private String buildLogContent(LogContext context) {
		Method method = context.getMethod();
		RequestInfo request = context.getRequestInfo();

		// 1. 构建基础信息
		StringJoiner content = new StringJoiner("\n", "\n", "\n");
		content.add("========================================== Start ==========================================");
		content.add("ThreadName     : " + request.getThreadName());
		content.add("URL            : [" + request.getMethod() + "]" + request.getUrl());
		content.add("Class Method   : " + method.getDeclaringClass().getName() + "." + method.getName());

		// 2. 请求参数
		if (context.getArgs().length > 0) {
			String argsStr = Arrays.stream(context.getArgs())
					.map(arg -> arg != null ? arg.toString() : "null")
					.collect(Collectors.joining(", "));
			content.add("Request Args   : (" + argsStr + ")");
		} else {
			content.add("Request Args   : (none)");
		}

		// 3. 返回结果
		Object result = context.getResult();
		String resultStr = (result == null) ? "null" :
				(method.getReturnType() == void.class) ? "void" : result.toString();
		content.add("Response Args  : " + resultStr);

		// 4. 耗时信息
		content.add("Time-Consuming : " + context.getTimeConsuming() + " ms");
		content.add("=========================================== End ===========================================");

		return content.toString();
	}

	@Data
	private static class LogContext {
		private Method method;
		private Object[] args;
		private Object result;
		private long timeConsuming;
		private RequestInfo requestInfo;
	}

	@Data
	private static class RequestInfo {
		private String threadName;
		private String method;
		private String url;
	}
}